Repository navigation
feat: wire boxd into the app (#356) - #372
Conversation
A resume cleared the awake ceiling outright. With the #257 heartbeat that turned "ask, then let it sleep" into "awake in 4 h chunks": a connect wakes a sleeping box, the ceiling started over, and a job still running held the box for another whole ceiling. Now a resume from Suppressed carries the awake time from before the sleep, so the next BUSY tick is still past the ceiling and raises a fresh prompt. A user who woke the box gets the card; with nobody there, the box sleeps again once that prompt times out. Any other state still starts over on a resume. The resume mask reads a CPU-only job IDLE for its first few ticks, and an IDLE step ends the ceiling, so the service now ignores IDLE from a masked tick (Classifier::wake_masked, Ceiling::step_unless_masked). Without that the carried time was lost before the job was seen again; the new sequence test checks both halves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RemoteHosts only ever made ContainerHostDriver, keyed by container runtime, so a boxd host had no key at all: a delete fell back to the Docker driver (`DriverKey(host) ?? DriverKey()`), adopt dropped boxd hosts, and a missing container CLI read as a boxd host being gone. DriverKey is now an enum over a container runtime (with its Docker context) and a boxd org and account, and RemoteHosts holds any HostTerminalDriver. A boxd driver is made by name and never adopted or swept (machine names carry no build; see #284). The agent socket comes from the key, and a remote host never borrows the Mac's gh (OQ20). A descriptor naming a driver this build doesn't know is refused, never taken down with Docker. RemoteWorkrooms.create takes the key instead of a runtime and records `driver: "boxd"` with the org and the boxd account (`user_id`). The boxd driver now refuses to act on another account too: every personal account's org is nil, so after an account switch the org check passed and a machine read as "not found" was taken for gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sidebar's New Workroom menu gains a Remote section with boxd, and the picker a boxd row. The entry says why it can't be used from what is cheap to read in a menu body: the boxd CLI (~/.local/bin, Homebrew) and a Codaset sign-in. A boxd workroom takes the broker's repo-scoped tokens only (OQ20), so `gh auth login` is no way in there. Whether boxd is signed in is a CLI call, so the create makes it: RemoteHosts.key(for: .boxd) asks `boxd auth --json` for the active org and account, which the new base is made in, and a signed-out CLI fails naming `boxd auth login`. A create with no Codaset session stops before anything is made. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RemoteWorkrooms.clonePath always named /home/workroom, the container user's home. A boxd machine logs in as boxd, so the path now follows the driver key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A boxd box wakes on an ssh login, and the status sweep connects to every
remote workroom, so every sweep would have woken every box and kept it
billing. A background read of a boxd host now asks boxd first
(`machine get --json`, status `standby` or `hibernated`) and, when it is
asleep, fails with RepositoryRoutingError.asleep instead of connecting.
boxd is asked at most once per host per retryAfter window.
The row shows a moon ("asleep") rather than the unknown dot, and Changes
says to open the workroom to wake it. Opening the workroom (`activate`)
connects as before, which wakes the box. When boxd can't say (no CLI,
signed out, offline), the read connects as it always did.
The status field is from boxd's CLI docs; the gated live run checks it
on a real machine.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#356) Every connect handed the host's agent this Mac's ceiling settings, with ask on wherever the box can sleep, including provisioning's base and derive connects, whose prompts nothing watches: a long clone past the ceiling would ask, nobody would answer, and the base would sleep under it (TODOS, "A base gets ask mode with nobody watching"). Sending `ask: false` from those connects instead would race: the settings are the whole agent's, so it could land after a watched connection's `ask: true` (Debug provisioning connects one) and switch prompting off under it. So an unwatched connect now sends none, and a base keeps the agent's default, which never asks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…an hold it (#356) The agent keeps a busy box awake with a heartbeat once a minute. A box whose boxd auto-suspend or auto-hibernate timer is shorter than about 90 seconds can be slept between two sends, under a running job, with nothing reporting it (TODOS, "An idle window under 60 s defeats the heartbeat silently"). On every connect to a boxd host the app now reads the machine's timers (`machine get --json`, the shorter of the two that are set) into the host's wakefulness model. A busy box with a window under 90 s shows as "busy but not kept awake", and the badge's help names the window and says to raise the auto-suspend timeout; an idle box carries the same note. The timers' encoding isn't documented, so numbers and unit strings (`120s`, `2m`, `1h`) are read and anything else never warns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Several hosts asking at once stacked their cards with no limit, so one could be pushed off the window while its countdown ran, and a prompt nobody answers lets that box sleep. The cards now go soonest deadline first, and past two they scroll in a bounded area. A stalled wakefulness service whose last reading was IDLE showed "idle" on a host that sleeps; that reading may be stale while work runs, and nothing is keeping the box awake either way. It now shows "status unknown" with a warning. A host that never sleeps still shows idle. The scroll area itself is a view change with no UI test; the ordering and the display rule are unit-tested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A container create shows its image download on the project row; a boxd create (a new machine, its setup and the clone, then a snapshot, a restore, a reboot, enrolment and checkout) showed a bare spinner for 20 s or more, which reads as a hang and invites a retry of a create that was working. The boxd driver and the provisioning sequence now report each step through a task-local (`RemoteProvisioning.$reportStep`, the pattern `ContainerHostDriver.$pullProgress` set), and a boxd create turns it into a progress ring with the step's name: "Copying the base machine (step 1 of 5)". Container creates report no steps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four BoxdIntegrationTests cases, gated like the others (TEST_RUNNER_WR_BOXD_TESTS=1, WR_AGENT_LINUX=1, sandbox off): - a remote workroom made and taken down through RemoteWorkrooms.create and delete on a boxd key: base in /home/boxd, records naming boxd with the org and account, nothing left after; - #257's keep-awake acceptance, rerunnable (TODOS): a control box with no agent sleeps mid-job, the agent's box logs every tick and sleeps after the job; - eng review D8: with the app attached (a watched connection, ssh keepalives, a badge-style status poll every 10 s), an idle box still sleeps, and a busy box whose prompt nobody answers still sleeps. They make paid machines and take 5-10 minutes each, so they run only when asked for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the design doc's "As built (#356)" entry: the driver-key enum, the account check, broker-only credentials, leaving asleep boxes alone, the ceiling across a wake, no boxd sweep, and the gated live runs that decide whether the entry can ship as is. Removes the two TODOS.md entries this branch completes (the P1 "Before wiring boxd into the app" items and the keep-awake acceptance case) and repoints the comments that cited them. The live sleep tests get more time: a fresh boxd machine's start-up traffic holds its timer for about 290 s (#257's acceptance). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The live runs showed a boxd box never sleeps while the app is attached: boxd counts inbound traffic as activity, and the service connection's ssh keepalives (every 15 s) and the badge's status polls (every 10 s) are inbound. Every open boxd workroom stayed awake and billing, and an unanswered prompt could not let its box sleep (eng review D8). When a connected boxd host's agent reports IDLE (its published verdict, which an unanswered prompt also makes IDLE), and its workroom is not the selected one and forwards no port, RemoteHosts now closes its connection and marks it let go of. A background read never reconnects it (it reads as asleep), and the badge, finding no connection, sends nothing. Selecting or opening the workroom takes it back. A remote pane's own ssh still keeps its box awake, which is the user working there. The two app-attached live tests now attach through RemoteHosts, with the badge's poll reporting to it, as the app does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dle boxes (#356) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From /ship's pre-landing review (specialists, red team, core pass): - A click wakes what a background read leaves alone: closing a pane and Keep awake call ensureConnected(_:wake:), so a boxd host let go of as idle, or asleep, is connected rather than refused. Before, closing a pane of such a workroom left its session running on the box. - Selection is tracked per window (one AppStore per window), including a selection restored at launch; a closed window drops its entry. Before, one window's selection could un-protect another window's workroom. - observed() re-checks the selection under the lock after its await, so a workroom selected meanwhile is never let go of. - isAsleep treats a CLI that answered with an error (signed out, another account, not found) as asleep for background reads, so they leave the box alone; only a CLI that couldn't answer lets the read go ahead. - Status reads time out after 10 s, not the CLI's 120 s. - One agent-socket lookup and one place-name mapping; the asleep message is shared; a misplaced doc comment moved. Tests: the click wake and per-window selection (both seen failing with the fix removed), the signed-out message, help text combinations, an anchored "unwatched connect sends nothing" check, and the coverage audit's additions (let-go reporting, selection from AppStore, asleep mapping, idle window on connect, step reports, port-forward exception). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A click (wake) is tried whatever the last attempt did: it no longer waits out the 30 s failure window, which a box failing once while it resumes would hit, and it clears that failure. - observed() checks once more after its last await that the host is still let go of and unselected before dropping the connection, so a selection or click across that wait keeps it connected. - A machine boxd answers "not found" for reads as gone (deleted, or in an org or account that isn't active), not asleep: background reads say so and leave it for the failure window. Other CLI errors still leave the box alone. - One display(hostSleeps:idleWindow:), the idle-window sentence appended in one place, and the picker's remote places in one branch; a comment updated for the wake path; errorLine read once. Tests: a second click inside the failure window, a gone machine, two real AppStore windows and a closed window letting its box go, and the unwatched-connect check now settles past the watched request. Each was seen failing with its fix removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Red Team pass 3 of the pre-landing review found that a boxd host was let go of only while a badge was polling it, and never looked at again once it was: - A connected boxd host is now polled for as long as it is connected (WakefulnessModel.pollWhileConnected), so an IDLE reading lets it go when its row is collapsed or scrolled away. The poll stops when the host is let go of or forgotten. - A host let go of is asked about again once its idle window has passed. If boxd says it is still awake, work holds it, so it is reconnected and its ceiling prompts are heard. - A stalled service's IDLE no longer lets a box go: it may be stale while work runs. - A selected workroom's box is reached by background reads even when asleep or let go of, so it doesn't read as asleep after the Mac wakes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Letting go of idle boxes: - A host with a pane open is never let go of: its pane's ssh holds the box awake anyway, so letting go only blanked its status. - A host whose agent's wakefulness service has stopped is let go of, as nothing on it holds the box awake. - IDLE readings within 10 s of a connect are ignored, so the read that connected the host is answered before its connection closes. - A box let go of reads "idle, not connected" (RepositoryRoutingError.idle, VCSStatusFailure.idle), not "asleep", until boxd confirms it asleep. - The let-go set and its timestamps are one map. Fixes: - An idle timer boxd reports past a week, or not finite, reads as unknown: as a Duration it trapped the revisit arithmetic. - A failed idle-window read keeps the window last read. - A new boxd host needs boxd auth's user_id, so a delete always checks the account. Cleanups: DriverKey.isBoxd replaces runtime == nil, the boxd home paths come from BoxdHostDriver.user, WorkroomPlace wraps RemoteWorkrooms.Place, an unreachable asleep case goes, and the wr-agent service and its test share one ceiling_step. Tests cover each change (each mutation-checked), the 90 s boundary, the stalled reading with a positive control, and the coverage audit's gaps. The design doc records the selected workroom's ceiling limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Red Team, review pass 2: - A pane registers its session when it is made, but a restored one spawns nothing until it is shown, so counting registrations kept every relaunched boxd workroom with saved panes connected, and billed. Only a pane that asked for its attach command now counts (PersistentSessionService.hasAttachedPane), and closing it stops counting even when the host's kill fails. - A service that stopped while its last reading was BUSY keeps its box: the connection is then all that keeps the job's box awake. - The asleep message names the boxd auth case, which also reads asleep. - The connect stamps its time before its poll starts, so the first reading always sees the grace. Also: the let-go state is letGoAt/isLetGo, the grace is pinned to the poll interval, a failed idle-window read is tested, the timer cap is pinned at a week, the boxd agent socket default is inline, two stale comments are fixed, and the design doc's limit says what holds a box. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Red Team, review pass 3: - BoxdHostDriver.idleWindow throws when boxd can't say, so a connect keeps the last window only on a failure; an answer with no timer set now clears it, and the badge stops warning about a timer turned off. - A stopped service's last BUSY holds its box for one ceiling at most: that reading never changes and nothing will ask about it. Also: the observed() and asleep docs say what they now check, BoxdHostDriver.stateDirectory builds the boxd state paths, the tests share one boxd workroom helper, wait for the idle-window read rather than sleeping, settle before their negative assertions, and cover a forgotten or unreachable pane holding nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A closed window or a dead link no longer keeps an idle box connected. Failed and stalled readings hold a box for one ceiling at most, as a stopped one already did. Also: the let-go grace is the poll interval, a boxd driver made without its CLI is not cached, a presence read no longer needs `source`, and tests for the click call sites, the hold across polls and a keep on the resume tick. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A pane pointed at another session lets the old one's host go, and a pane attaching to a host the app let go of takes it back, so its prompts are heard. The untrusted hold counts only readings that reached the agent, restarts after a trusted one, and falls back to the ceiling in preferences. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A request the app never sent, like one refused for a let-go host, no longer starts the untrusted hold, and a connect starts a new count. A take-back runs only while its pane still holds the box, and a click during a let-go's disconnect still takes the host back. A surface that fails to start, or one freed with its window, marks its pane detached at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Attaches are kept per pane, by a token the pane owns, so a pane freed late cannot clear a live pane's mark for the same session, and a freed pane's detach hops to the main actor safely instead of trapping off it. Letting a box go now checks its mark last, after every wait, so a click or a pane in between takes it back and keeps its connection. Tests pin that failed reads which reached the agent start the hold's clock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A let-go taken back resumes the host's poll without restarting the untrusted hold's count, which only a connect does. The surface's attach token is pinned by the wiring test, and comments and test notes match the per-pane attaches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…boxd cannot vouch for (#356) A derive or base create whose rollback leaves its machine running now keeps the entry, failed, naming the machine, so a delete can take it down instead of a paid machine running on with nothing to find it. leftBehind carries the host for that. A background read of a boxd box that boxd cannot say about (no CLI, a timeout, unexpected output, a cancelled read) now leaves it be and logs why, rather than connecting and waking it. Letting a box go rechecks port forwards and gives way to a newer BUSY reading at its last check, and the untrusted hold runs on a monotonic clock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…356) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iling (#356) A base build that fails after its machine exists, and whose rollback cannot destroy it, now records the machine on the project as the driver's own undo does. A record that fails is logged with the machine named. A boxd delete also removes the derive snapshot a failed derive can leave. The untrusted hold sanitises the wire ceiling before it reaches Duration, which trapped on an out-of-range value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d every left-behind record logs (#356) - BoxdHostDriver.destroy removes the snapshot best-effort: the machine is gone, so a stuck snapshot is logged rather than thrown. - Every record of something left live goes through recordLeftBehind, so a record that fails too names the machine and grant in the log. - A negative or non-finite ceiling on the wire is dropped; zero still counts. - Tests: snapshot removal names the machine, a stuck snapshot still destroys, a negative ceiling holds, and a failed base record keeps the original error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…356) presence() took a missing `status` as awake, so a background read would connect and its ssh login would wake a box boxd had not vouched for. A reply without a status is now "can't say" (nil), which RemoteHosts takes as asleep and logs, as for any other answer it did not expect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#356) The places loops listed only Docker and Apple Container, so a change that dropped boxd from New Workroom, or enabled it while its title gave a reason, passed every test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- GhosttySurfaceView: the three pane-detach sites share one helper, so a new way out of a pane can't miss the call that frees its host. - RemoteWorkrooms: create() and RemoteHosts docs say a boxd machine is never swept (#284); adopt()'s local no longer shadows `keys`. - Tests: prompt cards with no deadline or a tied one keep one order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…here a failed read clears (#356) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ccount (#356) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1392ee5524
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
🟡 Changes recommended
Cancellation can be swallowed during boxd cleanup, and the idle-disconnect path still has an unresolved take-back race.
5 open findings
What changed in this PR
Adds preview-gated boxd workroom creation, connection, lifecycle management, and deletion to the macOS app.
Changes:
- Introduces boxd-aware driver routing, identity, provisioning, and cleanup.
- Adds sleep-safe connection handling, wakefulness updates, and pane/forward holds.
- Extends UI status, progress, documentation, and automated coverage.
| File | Description |
|---|---|
TODOS.md |
Records a wakefulness test flake. |
docs/designs/remote-workrooms.md |
Documents the boxd architecture. |
macapp/AGENTS.md |
Updates app engineering guidance. |
Core/AgentPortForwarding.swift |
Exposes active-forward state. |
Core/AgentWakefulness.swift |
Adds idle-window and connection polling logic. |
Core/AppStore.swift |
Wires boxd creation and progress. |
Core/BoxdHostDriver.swift |
Adds account, presence, timers, and cleanup support. |
Core/ContainerHostDriver.swift |
Adapts leftover error handling. |
Core/GhosttySurfaceView.swift |
Tracks pane attachment lifetimes. |
Core/HostDriver.swift |
Extends leftover errors with host identity. |
Core/Models.swift |
Persists boxd org and account. |
Core/RemoteProvisioning.swift |
Reports provisioning steps. |
Core/RemoteWorkrooms.swift |
Implements boxd routing and idle release. |
Core/RepositoryLocation.swift |
Adds asleep, idle, and gone errors. |
Core/Session/AgentBootstrap.swift |
Supports unwatched connections. |
Core/Session/PersistentSessionService.swift |
Tracks attached remote panes. |
Core/WorkroomStatus.swift |
Presents asleep and idle states. |
Core/WorkroomStatusResolver.swift |
Maps new routing states. |
Views/ChangesPanel.swift |
Shows wakefulness warnings and unknown state. |
Views/NewWorkroomDialog.swift |
Adds boxd as a destination. |
Views/ProjectSidebar.swift |
Adds boxd actions and progress. |
Views/ToastStack.swift |
Orders and scrolls prompt cards. |
AgentWakefulnessTests.swift |
Covers polling and idle-release behavior. |
BoxdHostDriverTests.swift |
Covers boxd identity, presence, and cleanup. |
BoxdIntegrationTests.swift |
Adds live boxd lifecycle scenarios. |
ContainerProvisioningIntegrationTests.swift |
Updates error matching. |
GhosttySurfaceViewTests.swift |
Tests pane attachment lifetime. |
RemoteHostsTests.swift |
Covers routing, presence, and release rules. |
RemoteProvisioningTests.swift |
Tests provisioning progress. |
RemoteWorkroomRoutingTests.swift |
Tests status and user-action routing. |
Session/AgentBootstrapTests.swift |
Tests unwatched connection settings. |
Session/PersistentSessionAttachGateTests.swift |
Verifies pane token wiring. |
NewWorkroomPlacesUITests.swift |
Verifies boxd UI availability. |
wakefulness.rs |
Preserves ceiling state across resumes. |
wakefulness/tests.rs |
Tests resumed ceiling behavior. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…ac TODO (#348) #348 (a second Mac finds a remote workroom and reaches its host) is closed as not needed, so the Phase 4 gate (#260) no longer asks for the different-Mac test, and its P1 TODO goes with it. Open question 8 stays built (#255) and proven on the ssh fixture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex: - A let-go's disconnect now asks, on the connection manager, whether the host was taken back meanwhile (`disconnect(_:unless:)`), so a click or selection that lands across the hop keeps the connection it found up. Copilot: - A cancelled delete no longer reads as done when the cancel lands during the snapshot removal: cancellation is rethrown before the best-effort catch. - A presence read boxd couldn't answer is remembered for `retryAfter`, as asleep is, so an outage asks boxd once a window, not on every read. - The failed-record log names "the workroom as destroyed" when nothing is left live, instead of an empty list. - Test comments: the `eventually` doc is back on `eventually`, and a timed-out presence read is documented as nil taken as asleep, not "the read goes ahead". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fd955b02ed
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…l back (#356) Codex: connect's idle-window read could land after a delete had forgotten the host, and its `model(forHost:)` lookup made the model anew, watching for prompts for good. The read now updates the model the connect made, and only while it is still the host's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…age before fixing Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>


Fixes #356
Summary
You can now create, open and delete a remote workroom on boxd from the app, not only on a Docker or Apple container on this Mac. This is behind the remote-workrooms preview (Nightly and Dev builds).
boxd auth login. A create shows its steps (machine, setup, clone, enrol, checkout) in the sidebar's progress ring.RemoteHostsholds drivers byDriverKey, which is either a container runtime and its context, or a boxd org plus account. The account is checked as well as the org, because every personal account's org is nil. Records carrydriver: "boxd"with org and account. A relaunch reaches a boxd workroom by name, with no Docker needed. Delete goes through the boxd driver, and an unknown driver is refused rather than handed to Docker.boxd machine get. A box boxd calls asleep, or one it can't vouch for, is left alone. That covers a missing status, a CLI error and a timeout, and it is logged. Opening the workroom, selecting it or clicking it still connects.failed, so the project shows it and delete can finish it. A record that also fails is logged with the machine named.destroyalso removes a derive's snapshot, best-effort and logged.watched: false).Test Coverage
Coverage: 71% value-weighted (20/28 paths with a ★★ or ★★★ test), 75% including 1 weakly covered path.
Coverage gate: 71%. 7 paths are intentionally untested: each needs a test-only hook in the app, or is deferred to #371.
Test value: 0 tests written, 0 rejected by the authoring gate, 1 existing test extended (
NewWorkroomPlacesUITests, boxd rows; renaming the boxd entry turned 2 of its 3 tests red, and the picker ID 1), 1 path weakly covered.Pre-Landing Review
Three review passes ran in this /ship run, each with 5 specialists plus Red Team, and two earlier /ship runs did the same. The last pass made no edits.
RemoteHostsdocs no longer say leftovers get swept, andadopt()no longer shadowskeys.statusnow counts as "can't say" instead of awake (7555c9d).destroy.Duration.step_unless_masked; mergeremaininganddescribing; keep the idle window in one place;ViewThatFitsfor prompt cards; merge theleftBehindandrollbackIncompletecatches.BrokerReverseForwards.swift) and is noted here for you. Release and Nightly are unaffected.Exploratory QA
Functional smoke probes ran after each fix round:
RemoteHostsTests,AgentWakefulnessTests,BoxdHostDriverTestsandRemoteWorkroomRoutingTests, plusRemotePaneReconnectTestsin round 2, captured with gstack-qa-evidence: 178/0, 182/0 and 178/0.testADeriveThatFailsAtEachStepLeavesNoMachineOrSnapshotpassed (90 s).testARemoteWorkroomIsMadeAndTakenDownOnBoxdAsTheAppDoesItpassed (38 s).boxd machine listandsnapshots listwere empty afterwards.Design Review
The boxd place reuses the existing New Workroom places UI. No new design review was run.
Eval Results
No prompt-related files changed, so evals were skipped.
Scope Drift
Scope check: drift was found, all of it approved during review. The plan's scope was create, open and delete on boxd. The branch also adds:
.unknownbadge statePlan Completion
The plan had 26 items: 20 done, 6 done another way. All were addressed.
key(for: .boxd), driver and environment,create(key:driver:), HostDescriptordriver=boxdwith org and accountagentSocket(of: driver)andclonePath(for:on:).DriverKeyis.boxd(org:account:).watched: false).Verification Results
Plan checks: 4 PASS, 0 FAIL, 0 BLOCKED, 0 NOT RUN.
resumed()carry: passed in an earlier run;wakefulness.rsis unchanged since.Known and accepted:
testAnIdleBoxStillSleepsWithTheAppAttached) fails because a fresh boxd box reads BUSY for minutes (wr-agent: a freshly created boxd box reads BUSY for minutes with nothing running #369).TODOS
No TODO items completed in this PR. The branch removed the two it finished, the wake-on-connect P1 and the keep-awake acceptance P2. It adds a P0:
AgentWakefulnessTests.testAReplyCrossingAKeepDoesNotResurrectTheCardflakes. That test came from #216 on master, and it failed once in a targeted QA run.Documentation
Status: updated. One factual edit, no blockers.
Scope: base 8bb1d33 to HEAD 31031eb (33 commits, 34 files) plus uncommitted TODOS.md; branch feat/boxd-in-app (#356, boxd in the app). Steps 1, 1.5, 2-4 and 6 only. Docs discovered per the candidate list (README, AGENTS.md files, CONTRIBUTING, DESIGN, PRODUCT, docs/agents, docs/designs, docs/repository-notes.md) and the nested macapp/AGENTS.md.
Documentation health:
RemoteHostsDriverKey, that a background read of an asleep boxd host leaves it asleep, and that idle hosts are let go of so boxd can sleep them. It points to 'As built (Wire boxd into the app: create, open and delete remote workrooms on boxd #356)'. Verified againstBoxdHostDriver.checkOrg,RemoteHosts.DriverKeyandRemoteWorkroomscode.leftBehind/ failed-record text match final code (30 s retry, 10 s poll and let-go grace, 90 s idle-window warning, idle window plus 30 s revisit, nine live tests, snapshot cleanup, preview gate). Remote workrooms: recovering a project whose boxd base was left behind #370 exists and matches.RemoteWorkroomUnsupportedrow is still true for the Go CLI.Coverage debt: common gaps, not filled. New Workroom > Remote > boxd and boxd idle let-go/sleep behaviour have reference and explanation in the design doc and macapp/AGENTS.md but no how-to or tutorial. No critical gaps. Diagram drift: none found. Suggest /document-generate if a boxd how-to is wanted.
Pre-existing debt: macapp/AGENTS.md 'Remote workrooms in config (#249)' still says the app's remote deletion is refused until #253; #253 shipped as #289 and #356 deletes boxd workrooms in the app. Left for the author because it is a narrative rewrite.
Test plan
make app-test: 3267 passed, 0 failed (31031eb; the later commits change only TODOS.md and macapp/AGENTS.md)cargo test -p wr-agent(Rust 1.96.1): passmake app-uitest APP_UITEST_FLAGS=-only-testing:WorkroomAppUITests/NewWorkroomPlacesUITests: 3/3TEST_RUNNER_WR_BOXD_TESTS=1 WR_AGENT_LINUX=1, sandbox off): 2/2 affected cases, nothing left on boxdmake app-format && make app-lint: clean🤖 Generated with Claude Code